
extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include "string.h"
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/select.h>
#include "fbviewer.h"

#include <linux/input.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <termios.h>



#include "opts.h"
#include "font.h"
#define DR_WAV_IMPLEMENTATION
#include "dr_wav.h"
#include "benchmark.h" 
#include "lm_benchmark.h" 
#include "ms_asr.h"

extern void decoder_kws_test(void);
extern void am_infer_test(void);
extern void mic_test(void);
}

#define DEV_PATH "/dev/input/event0" //keys
//#include "opencv2/opencv.hpp"
//using namespace cv;

#define DBG_LINE()  //printf("### L%d\n", __LINE__)
#define DBG(format, ...) printf(format, __VA_ARGS__)

int fb_ok=1;
int font_bit=0;

#define LCD_W 240
#define LCD_H 240
#define lcd_show(a,b,c,d,e,f,g,h)  if(fb_ok==1)fb_display(a, b, c, d, e, f, g, h)

uint8_t lcd_buf[LCD_W*LCD_H*4];

static int l_bpp = 24;
volatile int exit_flag = 0;
static struct termios ori_attr, cur_attr;

static int tick = 0;
void my_rawcb(void* data, int len)  //data struct: pnyp_t data[len][BEAM_CNT]
{
    pnyp_t* res = (pnyp_t*)data;
    printf("===================================\n");
    for(int t=0; t<len; t++) {
        pnyp_t* pp = res+BEAM_CNT*t;
        if(1){ 
            printf("T=%04d ====:", tick);
            tick += 1;  
            for(int i=0; i < 3; i++) { //BEAM_CNT
                printf("  %4d %-6s: %.3f;", pp[i].idx, am_vocab[pp[i].idx], ((float)(pp[i].p)));
            }
        }
        printf("\n");
    }
    printf("####\n");
    return;
}

static __inline  int tty_reset(void)
{
   if (tcsetattr(STDIN_FILENO, TCSANOW, &ori_attr) != 0)
      return -1;

   return 0;
}


static __inline  int tty_set(void)
{

   if ( tcgetattr(STDIN_FILENO, &ori_attr) )
      return -1;

   memcpy(&cur_attr, &ori_attr, sizeof(cur_attr) );
   cur_attr.c_lflag &= ~ICANON;
//        cur_attr.c_lflag |= ECHO;
   cur_attr.c_lflag &= ~ECHO;
   cur_attr.c_cc[VMIN] = 1;
   cur_attr.c_cc[VTIME] = 0;

   if (tcsetattr(STDIN_FILENO, TCSANOW, &cur_attr) != 0)
      return -1;

   return 0;
}




static __inline  int kbhit(void)
{

   fd_set rfds;
   struct timeval tv;
   int retval;

   /* Watch stdin (fd 0) to see when it has input. */
   FD_ZERO(&rfds);
   FD_SET(0, &rfds);
   /* Wait up to five seconds. */
   tv.tv_sec  = 0;
   tv.tv_usec = 1000;

   retval = select(1, &rfds, NULL, NULL, &tv);
   /* Don't rely on the value of tv now! */

   if (retval == -1) {
      perror("select()");
      return 0;
   } else if (retval)
      return 1;
   /* FD_ISSET(0, &rfds) will be true. */
   else
      return 0;
   return 0;
}

/*
int lcd_show(unsigned char *rgbbuff, unsigned char * alpha,
               unsigned int x_size, unsigned int y_size,
               unsigned int x_pan, unsigned int y_pan,
               unsigned int x_offs, unsigned int y_offs);
*/
//DIGIT_LOG_LEN
#define DIG_H 32
void my_digitcb(void* data, int len)
{
	if(len>0)
	{
		char* digit_res = (char*) data;
		char digit_res1[32];
		memset(digit_res1, ' ', 32);
		digit_res1[32-1]=0;
		memcpy(digit_res1, digit_res, strlen(digit_res));
		printf("digit_res1: %s\n", digit_res);

		memset((uint8_t*)lcd_buf+32*LCD_W*l_bpp/8, 0, LCD_W*(LCD_H-32)*l_bpp/8);
		//font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 0, FONT_C_WHITE, FONT_C_BLACK, (char*)"DIGIT");
		int last_y = font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 32, FONT_C_YELLOW, FONT_C_BLUE, digit_res);
		lcd_show(lcd_buf, 0, LCD_W, last_y, 0, 0, 0, 0);
	}
    return;
}


void my_lvcsrcb(void* data, int len)
{
	(void)len;
	char* words = ((char**)data)[0];
	char* pnys = ((char**)data)[1];		
	if((pnys[0]!=0)||(words[0]!=0))
	{
		memset(lcd_buf+32*LCD_W*l_bpp/8, 0, LCD_W*(LCD_H-32)*l_bpp/8);
		//font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 0, FONT_C_WHITE, FONT_C_BLACK, (char*)"LVCSR");
		int last_y = font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 32, FONT_C_YELLOW, FONT_C_BLUE, words);
		int flash_h = last_y>LCD_H ? LCD_H : last_y;	
		lcd_show(lcd_buf, 0, LCD_W, flash_h, 0, 0, 0, 0);
		printf("PNYS: %s\nHANS: %s\n",pnys, words);
		if(strstr(words,"退出."))
		{
			exit_flag=1;	
		}
	}
    
}

//int font_draw(uint8_t* fb, int lcd_w, int lcd_h, int char_size, int x_oft, int y_oft, int c_color, int bg_color, char* str);
/*int lcd_show(unsigned char *rgbbuff, unsigned char * alpha,
               unsigned int x_size, unsigned int y_size,
               unsigned int x_pan, unsigned int y_pan,
               unsigned int x_offs, unsigned int y_offs) */
char* my_kw_tbl[3]={
    (char*)"xiao3 ai4 tong2 xue2",
    (char*)"tian1 mao1 jing1 ling2",
    (char*)"tian1 qi4 zen3 me yang4"
};
char* my_kw_str[3]={
    (char*)"XIAO AI TONG XU",
    (char*)"TIAN MAO JING L",
    (char*)"TIAN QI ZEN ME"
};
float my_kw_gate[3] = {0.1, 0.1, 0.1};
void my_kwscb(void* data, int len)
{
    float* p = (float*) data;
    float maxp = -1;
    int   maxi = 0;
    for(int i=0; i<len; i++){
        printf("\tkw%d: %.3f;", i, p[i]);
        if(p[i] > maxp){
            maxp = p[i];
            maxi = i;
        }
    }
    printf("\n");

    memset(lcd_buf+32*LCD_W*l_bpp/8, 0, LCD_W*(LCD_H-32)*l_bpp/8);
    //font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 0, FONT_C_WHITE, FONT_C_BLACK, (char*)"KWS");
    if(maxp > 0.1){
        font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, (maxi+1)*32, FONT_C_YELLOW, FONT_C_BLUE, my_kw_str[maxi]);
    }
    lcd_show(lcd_buf, 0, LCD_W, 32*5, 0, 0, 0, 0); 
    return ;
}


void my_handler(int s){
    printf("Caught signal %d\n",s);
    exit_flag = 1;
    return;
}


void font_test(void)
{   
    char test_data[3] = {(char)0xc8, (char)0x41, (char)0};
    font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 16, 0, 0, FONT_C_RED, FONT_C_BLACK, (char*)"�ֿ����123abc");
    font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 24, 0, 16, FONT_C_GREEN, FONT_C_BLACK, (char*)"�ֿ����123abc");
    font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 16+24, FONT_C_BLUE, FONT_C_BLACK, (char*)"�ֿ����123abc");
    font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 48, 0, 16+24+32, FONT_C_WHITE, FONT_C_BLACK, (char*)"�ֿ����123abc");
    font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 48, 0, 16+24+32+48, FONT_C_WHITE, FONT_C_BLACK, test_data);
    lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
    return;
}

int lcd_init(void)
{
    l_bpp = fb_get_bpp();
    if(l_bpp==16)l_bpp=24;
    if(l_bpp==-1){l_bpp=24;fb_ok=0;}
    printf("### bpp=%d\n", l_bpp);
    return fb_ok;
}

void lcd_clear(void)
{
    //while(1)
    {
        memset(lcd_buf, 0xff, LCD_W*LCD_H*l_bpp/8);
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
        //sleep(1);
        memset(lcd_buf, 0x00, LCD_W*LCD_H*l_bpp/8);
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
        //sleep(1);
    }
    return;
}

void lcd_clear1(void)
{
    //while(1)
    {
        memset(lcd_buf+LCD_W*32*l_bpp/8, 0xff, LCD_W*LCD_H*l_bpp/8);
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
        //sleep(1);
        memset(lcd_buf+LCD_W*32*l_bpp/8, 0x00, LCD_W*LCD_H*l_bpp/8);
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
        //sleep(1);
    }
    return;
}

static int keys_fd;
void key_init(void)
{
    keys_fd=open(DEV_PATH, O_RDONLY);
    if(keys_fd <= 0)
    {
        printf("open /dev/input/event0 device error!\n");
        return;
    }
}

int key_read(void)
{
	if(keys_fd > 0)
	{
		int res = 0;
		struct input_event t;
		struct timeval timeout;
		fd_set set;
		int rv;
		timeout.tv_sec = 0;
		timeout.tv_usec = 1000;
		FD_ZERO(&set); /* clear the set */
		FD_SET(keys_fd, &set); 
		rv = select(keys_fd + 1, &set, NULL, NULL, &timeout);
		if(rv == -1) {
			perror("select"); /* an error accured */
		} else if(rv == 0) {
			// printf("key timeout; "); /* a timeout occured */
		} else {
			int len = read(keys_fd, &t, sizeof(t)); /* there was data to read */
			if(len==sizeof(t)) {
				if(t.type == EV_KEY) {
					if(t.value == 0 || t.value==1){
						printf("key %d %s\n", t.code, (t.value) ? "Pressed" : "Released");
						if(t.value==1) res = 1;
					}
				}
			}
		}
		return res;
	}
	return 0;
}

void key_deinit(void)
{
	if(keys_fd > 0)
		close(keys_fd);
}

void set_dig(int flag)
{
    size_t decoder_args[10]; 
    int res = 0;
    if(flag){ //init
        decoder_args[0] = 640;  //blank_t ms
        res = ms_asr_decoder_cfg(DECODER_DIG, my_digitcb , &decoder_args, 1);
        if(res != 0) {printf("DECODER_DIG init error!\n");};
        lcd_clear(); 
        font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 0, FONT_C_WHITE, FONT_C_BLACK, (char*)"DIGIT");
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
    } else {
        ms_asr_decoder_cfg(DECODER_DIG, NULL , NULL, 0);
    }
    return;
}

void set_kws(int flag)
{
    size_t decoder_args[10]; 
    int res = 0;
    if(flag){ //init 
        decoder_args[0] = (size_t)my_kw_tbl;
        decoder_args[1] = (size_t)my_kw_gate;
        decoder_args[2] = 3;
        decoder_args[3] = 1;  //auto similar //自动去除音调 近音处理
        printf("qqqq");
        res = ms_asr_decoder_cfg(DECODER_KWS, my_kwscb , &decoder_args, 3);printf("aaaa");
        if(res != 0) {printf("DECODER_KWS init error!\n");goto out1;};
        char* similar_pnys0[1] = {(char*)"xiang3"};    //每个最多注册10个近音词
        ms_asr_kws_reg_similar((char*)"xiao3", similar_pnys0, 1);
        char* similar_pnys1[3] = {(char*)"xin1", (char*)"ting1", (char*)"jin1"};
        ms_asr_kws_reg_similar((char*)"jing1", similar_pnys1, 3);
        lcd_clear(); 
        font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 0, FONT_C_WHITE, FONT_C_BLACK, (char*)"KWS   ");
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
    } else {
        ms_asr_decoder_cfg(DECODER_KWS, NULL , NULL, 0);
    }
out1:
    return;
}

void set_lvcsr(int flag)
{
    size_t decoder_args[10]; 
    int res = 0;
    if(flag){ //init 
        
        decoder_args[0] = (size_t)opts.sfst_name;
        decoder_args[1] = (size_t)opts.sym_name;
        decoder_args[2] = (size_t)opts.phones_txt;
        decoder_args[3] = (size_t)opts.words_txt;
        memcpy(&decoder_args[4], &(opts.beam), sizeof(float));
        memcpy(&decoder_args[5], &(opts.bg_prob), sizeof(float));
        memcpy(&decoder_args[6], &(opts.scale), sizeof(float));
        decoder_args[7] = (size_t)opts.is_mmap;//printf("#\n");
        res = ms_asr_decoder_cfg(DECODER_LVCSR, my_lvcsrcb , &decoder_args, 8);
        if(res != 0) {printf("DECODER_LVCSR init error!\n");};
        lcd_clear(); 
        font_draw(lcd_buf, l_bpp, LCD_W, LCD_H, 32, 0, 0, FONT_C_WHITE, FONT_C_BLACK, (char*)"LVCSR");
        lcd_show(lcd_buf, 0, LCD_W, LCD_H, 0, 0, 0, 0);
    } else {
        ms_asr_decoder_cfg(DECODER_LVCSR, NULL , NULL, 0);
    }
    return;
}


int main(int argc, char const* argv[])
{	
	printf("==============================================\n");
	printf("MaixSense R329 ASR Demo\n");
	printf("Developed by Sipeed.   \n");
	printf("Email: support@sipeed.com\n");
	printf("Usage: ./maix_asr cfg_file\n");
	printf("config file contains key:value every line\n");
	printf("==============================================\n\n");

    signal(SIGINT, my_handler); 
    key_init();
//非阻塞getChar初始化
   int tty_set_flag;
   tty_set_flag = tty_set();
   
   
    int res = 0;
    //int demo_idx = 0;
    //parse opts
    char* cfg_file = NULL;
	if(argc < 2) {
		printf("argc=%d error\n", argc);
		return -1;
	}
    cfg_file = (char*)argv[1];
    if(parse_opts(cfg_file) != 0) {
        printf("parse_opts failed! check your cfg file\n");
        return -1;
    }
    if(lcd_init())
	{
		char font_path[100];
		//load fonts (optional)

		strcpy(font_path, opts.font_path); strcat(font_path, "16.FON"); 
		if((access(font_path,F_OK))!=-1){		
		res += font_reg(font_path, 16);
		font_bit|=1;
		}
		
		
		strcpy(font_path, opts.font_path); strcat(font_path, "24.FON"); 
		if((access(font_path,F_OK))!=-1){
		res += font_reg(font_path, 24);
		font_bit|=2;
		}
		
		strcpy(font_path, opts.font_path); strcat(font_path, "32.FON"); 
		if((access(font_path,F_OK))!=-1){
		res += font_reg(font_path, 32);
		font_bit|=4;
		}
		
		//font_test();
		lcd_clear();		
	}


    if(opts.testpath != NULL){
        benchmark(opts.testpath, opts.testpny, opts.testhan);
        return 0;
    }
    if(opts.testlm != NULL){
        lm_benchmark(opts.testlm);
        return 0;
    }

    //mic_test();return 0;

    //asr lib
    am_args_t am_args = {opts.model_name, opts.model_in_len, opts.strip_l, opts.strip_r, opts.phone_type, opts.agc};
    int dbg_flag = opts.dbg_micraw*DBG_MICRAW + opts.dbg_mic*DBG_MIC + \
        opts.dbg_strip*DBG_STRIP + opts.dbg_lvcsr*DBG_LVCSR +\
        opts.dbgt_pp*DBGT_PP + opts.dbgt_am*DBGT_AM +\
        opts.dbgt_kws*DBGT_KWS + opts.dbgt_wfst*DBGT_WFST;
    printf("dbg_flag = 0x%x\n", dbg_flag);
    res = ms_asr_init(opts.device_type, opts.device_name, &am_args, dbg_flag);

    if(res != 0) {
        printf("ms_asr_init error!\n");
        return -1;
    }

    //am_infer_test();
    //decoder_kws_test();
    //return 0;

    //Main Loop
    if(opts.do_raw) {
        res = ms_asr_decoder_cfg(DECODER_RAW, my_rawcb , NULL, 0);
        if(res != 0) {printf("DECODER_RAW init error!\n");goto free_decoder;};
    }

 #if 1
    if(opts.do_dig) set_dig(1);
    if(opts.do_kws) set_kws(1);
    if(opts.do_lvcsr) set_lvcsr(1);
    //
    while(!exit_flag) {
        int frames = ms_asr_run(1); //1 frame default 768ms(am_xxyy_192)
        if(frames<1) {
            printf("run out\n");
            break;
        }
         if (kbhit()) {
			 int key = getchar();
			 if (key == 'q')
				break;
            printf("Reset ASR state!\n");
            lcd_clear1();
            ms_asr_clear();
        }
    }
#else 
    lcd_clear();
    set_dig(1);
    while(!exit_flag) {
        int frames = ms_asr_run(1); //1 frame default 768ms(am_xxyy_192)֡
        if(frames<1) {
            printf("run out\n");
            break;
        }
         if (kbhit()) {
			 int key = getchar();
			 if (key == 'q')
				break;
            printf("Change ASR state!\n");
            demo_idx+=1;
            demo_idx = demo_idx%3;printf("000");
            if(demo_idx==0){
                set_dig(1); set_kws(0); set_lvcsr(0);
            } else if(demo_idx==1){
                set_dig(0); set_kws(1); set_lvcsr(0); 
            }else{
                set_dig(0); set_kws(0); set_lvcsr(1);
            }
        }
    }
#endif
    printf("Done~~~\n");
  
    //deinit resource
free_decoder:
    ms_asr_deinit();

	if(font_bit&1)
		font_unreg(16);
	if(font_bit&2)
		font_unreg(24);
	if(font_bit&4)
		font_unreg(32);		
    //font_unreg(48);
    key_deinit();
if (tty_set_flag == 0)
      tty_reset();
  
	return res;
}